package webrtcwrappers;

import android.view.View;

import com.example.myapplication.MainActivity;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RtpReceiver;
import org.webrtc.RtpTransceiver;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoTrack;

import java.util.List;
import java.util.concurrent.ExecutionException;

import json_object_mapper.Trickle;
import observer.SessionObserver;
import signal.Role;
import signal.Signal;

public class PeerConnectionCreator {
    private static final Logger logger = LoggerFactory.getLogger(PeerConnectionCreator.class);
    private PeerConnection pc;
    private final Signal signal;
    private final Role role;
    private DataChannel api;
    private final List<IceCandidateInit> iceCandidates;
    private MainActivity context;

    public PeerConnectionCreator(Role role, Signal signal,
                                 PeerConnection.RTCConfiguration config, PeerConnectionFactory peerFactory
            , List<IceCandidateInit> iceCandidates) {
        this.signal = signal;
        this.role = role;
        this.iceCandidates = iceCandidates;
        createPeerConnection(peerFactory, config);
    }

    public void renegotiate() {
        if (pc == null) {
            throw new IllegalStateException();
        }
        SessionObserver observer = new SessionObserver();
        pc.createOffer(observer, new MediaConstraints());
        try {
            if (observer.getSdp() != null) {
                SessionDescription offer = observer.getSdp();
                pc.setLocalDescription(observer, offer);
                SessionDescription answer = signal.offer(offer).get();
                pc.setRemoteDescription(observer, answer);
                logger.info("Answer for the publisher {}", answer.description);
                if (pc.getRemoteDescription() != null && iceCandidates != null) {
                    iceCandidates.forEach(iceCandidate -> this.pc.addIceCandidate(IceCandidateInit.convertToIceCandidate(iceCandidate)));
                }
            }
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void setContext(MainActivity context) {
        this.context = context;
    }

    private void createPeerConnection(PeerConnectionFactory peerFactory, PeerConnection.RTCConfiguration config) {
        this.pc = peerFactory.createPeerConnection(config, new PeerConnection.Observer() {
            @Override
            public void onSignalingChange(PeerConnection.SignalingState signalingState) {

            }

            @Override
            public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
                logger.info("Current ice state:{}, role: {}", iceConnectionState, role);
                if (PeerConnection.IceConnectionState.DISCONNECTED == iceConnectionState ||
                        PeerConnection.IceConnectionState.FAILED == iceConnectionState){
                    logger.info("ICE fail states...");
                }
            }

            @Override
            public void onIceConnectionReceivingChange(boolean b) {

            }

            @Override
            public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {

            }

            @Override
            public void onIceCandidate(IceCandidate iceCandidate) {
                IceCandidateInit iceCandidateInit = new IceCandidateInit(iceCandidate.sdpMid,
                        iceCandidate.sdpMLineIndex, iceCandidate.sdp, iceCandidate.serverUrl);
                Trickle trickle = new Trickle(role.getRole(), iceCandidateInit);
                try {
                    logger.info("sending ice candidate: {}", new ObjectMapper().writeValueAsString(trickle));
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }

                signal.trickle(trickle);
            }

            @Override
            public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {

            }

            @Override
            public void onAddStream(MediaStream mediaStream) {
                // TODO: find a correct way to render this to a view
                VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
                SurfaceViewRenderer remoteVideoView = context.getRemoteVideoView();
                remoteVideoTrack.addSink(remoteVideoView);
                remoteVideoView.setVisibility(View.VISIBLE);
            }

            @Override
            public void onRemoveStream(MediaStream mediaStream) {
                logger.info("onRemoveStream called");
            }

            @Override
            public void onDataChannel(DataChannel dataChannel) {
                api = dataChannel;
                logger.info("Create new data channel: {}", dataChannel);
                dataChannel.registerObserver(new DataChannel.Observer() {
                    @Override
                    public void onBufferedAmountChange(long l) {

                    }

                    @Override
                    public void onStateChange() {

                    }

                    @Override
                    public void onMessage(DataChannel.Buffer buffer) {

                    }
                });
            }

            @Override
            public void onRenegotiationNeeded() {
                logger.info("Renegotiation needed...role: {}", role);

            }
            @Override
            public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
                logger.info("On add track called");
            }

            @Override
            public void onTrack(RtpTransceiver transceiver) {
                logger.info("onTrack: {}", transceiver.getSender());
            }
        });

    }


    public PeerConnection getPc() {
        return pc;
    }

    public Signal getSignal() {
        return signal;
    }

    public Role getRole() {
        return role;
    }

    public DataChannel getApi() {
        return api;
    }

    public void setApi(DataChannel api) {
        this.api = api;
    }

}
